#ifndef GLFRAME_H
#define GLFRAME_H

namespace math {namespace ui {

template <typename frame_type> class GLApp ;
/**
 * GLFrame class. 
 * Provide the interfaces for GL application. 
 * This class cannot define object. The object should be constructed by GLApp. 
 */
class GLFrame 
{
protected :
	GLFrame () : m_win_width (1024), m_win_height(768), 
		prevX (0), prevY(0), 
		leftPressed (false), rightPressed(false), middlePressed (false)
	{
		float _rotMat [16] = {1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1};
		for (unsigned i = 0; i < 16; ++i) m_rotate_matrix[i] = _rotMat[i] ;
		focus [0] = 0 ;
		focus [1] = 0 ;
		focus [2] = -1.5; 
	}
	void get_win_size (unsigned & w, unsigned & h)
	{
		w = this->m_win_width ;
		h = this->m_win_height ;
	}
	void set_win_size (unsigned w, unsigned h)
	{
		this->m_win_width  = w;
		this->m_win_height = h; 
	}
	void init (int argc, char ** argv)
	{
		glutInit(&argc, argv);
		glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
		glutInitWindowPosition(0, 0);
		glutInitWindowSize(m_win_width, m_win_height);
		glutCreateWindow("DEMO");
		this->initGL (); 
	}

public :
	virtual void reshape (int width, int height)
	{
		glViewport(0, 0, width, height);
		m_win_width = width;
		m_win_height = height;
	}
	virtual void idle () 
	{
		glutPostRedisplay (); 
	}
	virtual void sence () 
	{
		glutSolidCube ( 1 );
	}
	virtual void set_viewport () 
	{
		// Set up viewing matrices
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		gluPerspective(60, float(m_win_width)/m_win_height, .0001, 100);
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

		//Camera
		glTranslatef(focus[0], focus[1], focus[2] - 7.0);
		glMultMatrixf(m_rotate_matrix);
	}
	virtual void display () 
	{
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		this->set_viewport ();

		this->sence(); 
	
		glFlush ();
		glutSwapBuffers();
	}
	virtual void mouse (int button, int state, int x, int y) 
	{
		y = m_win_height - y;

		// Mouse state that should always be stored on pressing
		if (state == GLUT_DOWN)
		{
			prevX = x;
			prevY = y;
		}

		if (button == GLUT_LEFT_BUTTON)
		{
			leftPressed = state == GLUT_DOWN;
		}

		if (button == GLUT_RIGHT_BUTTON)
		{
			rightPressed = state == GLUT_DOWN;
		}
	
		if (button == GLUT_MIDDLE_BUTTON)
		{
			middlePressed = state == GLUT_DOWN;
		}		
	} 
	virtual void motion(int x, int y) 
	{
		y = m_win_height - y;
		float dx = (x - prevX);
		float dy = (y - prevY);

		// rotate the scene
		if (leftPressed)
		{
			glMatrixMode(GL_MODELVIEW);
			glLoadIdentity();
			glRotatef(dx, 0, 1, 0);
			glRotatef(dy, -1, 0, 0);
			glMultMatrixf(m_rotate_matrix);
			glGetFloatv(GL_MODELVIEW_MATRIX, m_rotate_matrix);
		}
		else if (middlePressed)
		{
			focus [ 0 ] += 0.005 * dx;
			focus [ 1 ] += 0.005 * dy;
		}
		else if (rightPressed)
		{
			focus [ 2 ] += 0.01 * dy;
		}
	
		// Store previous mouse positions
		prevX = x;
		prevY = y;

		glutPostRedisplay();
	}
	virtual void keyboard(unsigned char key, int x, int y) 
	{
		glutPostRedisplay();
	}
	virtual void initGL () 
	{
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glViewport(0, 0, m_win_width, m_win_height);
		glClearColor(0, 0, 0, 1);   
		glEnable(GL_DEPTH_TEST);
		glEnable(GL_LIGHT0);
		glEnable(GL_LIGHTING);
		glEnable(GL_CULL_FACE);
	}
	
private :
	unsigned m_win_width , m_win_height ;

    // mouse state
	int prevX , prevY ;
	bool leftPressed , rightPressed , middlePressed ;

    // view state
	float m_rotate_matrix [ 16 ] ; 
	float focus [ 3 ] ; 

	template <typename frame_type> friend class GLApp ;

};


// This is a singleton class and it is a proxy to the class frame.  
template <typename frame_type>
class GLApp 
{
public :
	GLApp ()
	{
		if (m_frame == NULL)
			m_frame = new frame_type ;
	}
	~GLApp () {if (m_frame != NULL) delete m_frame ;}
	static void init ()
	{
		m_frame->init(0, NULL) ;
	}
	static void display  () { m_frame->display();}
	static void reshape  (int w, int h) { m_frame->reshape(w, h);}
	static void keyboard (unsigned char key, int x, int y) { m_frame->keyboard(key, x, y);}
	static void mouse    (int button, int state, int x, int y) { m_frame->mouse(button, state, x, y); }
	static void motion   (int x, int y) { m_frame->motion (x, y); }
	static void idle () {m_frame->idle () ;}
	static void set_win_size (unsigned w, unsigned h) {m_frame->set_win_size (w,h) ;}
	static void run () 
	{
		glutDisplayFunc(display); 
		glutReshapeFunc(reshape);
		glutIdleFunc(idle);
		glutKeyboardFunc(keyboard);
		glutMouseFunc(mouse);
		glutMotionFunc(motion);
		// main loop
		glutMainLoop();		
	}

private :
	static frame_type * m_frame  ;
} ;

template <typename frame_type>
frame_type * GLApp<frame_type>::m_frame = NULL ;	

}
}


/**
Example
 
class NewFrame : public math::ui::GLFrame
{
public :
	NewFrame () : GLFrame () {}
	virtual void sence () { glutSolidSphere(0.5, 40, 40) ;}
	
};

int main(int argc, char * argv[])
{
	math::ui::GLApp <NewFrame> app; 
	app.init ();
	app.run(); 
	return 0; 
}

*/
#endif
